#!BPY
"""
Name: 'Sketch (.sk)...'
Blender: 244
Group: 'Export'
Tooltip: 'Export selected objects to the Sketch scene description language'
"""

__author__ = 'Kjell Magne Fauske'
__version__ = "svn"
__url__ = ("Documentation, http://www.fauskes.net/code/blend2sketch/",
           "Author's homepage, http://www.fauskes.net/",
           "Sketch, http://www.frontiernet.net/~eugene.ressler/")


__bpydoc__ = """\
This script exports the selected objects to the Sketch scene description
language. Sketch is a small, simple system for producing line drawings of
two- or three-dimensional solid objects and scenes. Sketch outputs PGF/TikZ
or PSTricks code suitable for use in TeX/LaTeX documents. With this script
you can create objects in Blender and use them in your Sketch drawings.

Usage:

Select the objects you want to export and invoke the script from the
"File->Export" menu[1]. Alternatively you can load and run the script from
inside Blender.

Supported object types:<br>
    - Mesh<br>
    - Curve, Surf, MBall: Converted to mesh when exported<br>
    - Empties: Exported as a coordinates<br>
    - Material: Materials assigned to mesh and curve objects.<br>
    - Camera: The active camera will be used to set up a Sketch scene with a
viewpoint similar to Blender's.</br>

All drawable objects are exported as polygons or poly lines. Non-mesh objects
like curves and surfaces will be converted to a mesh when exported. Each face
in a mesh will be saved as a polygon. If the mesh does not has any faces,
edges will be saved as lines.

Materials:

The exporter has basic support for materials. By default the material's RGB
value is used as fill or draw color. An alternative is to specify polygon
and line options directly by putting the values in a 'polyoptions' and
'lineoptions' property assigned to the material. You can use the
Help->Property ID browser to set these values.

Issues:

- Meshes with faces and edge-only parts are not supported yet. Only the faces
  will be exported in this case.
- No support for grouping and parenting. All objects are currently exported as
  independent blocks. I will probably add support for groups in a future version.
- No support for fgons yet.

[1] Requires you to put the script in Blender's scripts folder. Blender will
then automatically detect the script.
"""

# Changelog:
# SVN
#   - Minor code refactoring
#   - Added group support.
# 1.0.1
#   - Fixed bug in names generated for lineoptions. Thanks Semmelb!
# 1.0.0 - First public release

# Ideas:
#
# - Flat shading. Should be implemented in Sketch, but is probably quite easy to
#   implement in the exporter as well.
# - Draw meshes with both faces and 'standalone' edges
# - FGONS support

import Blender
from Blender import sys as bsys
from Blender import Mesh, Mathutils, Registry, Scene, Material, Group

# Start of configuration section -------

REG_KEY = 'sketch_export'

# config options:

EXPORT_MATERIALS = True
OUTPUT_LANGUAGE = 'tikz'
EXPORT_CAMERA = True
STANDALONE = True
USE_TWOSIDED_FLAG = True
SPE_LINE_COLOR = False
ONLY_PROPERTIES = False
EXPORT_GROUPS = True

GLOBAL_COORDS = True
TRANSFORM_VERTICES = True

# new

tooltips = {
    #'GLOBAL_COORDS' : 'Transform mesh vertices to global coordianates',
    'EXPORT_MATERIALS' : 'Apply materials to mesh faces',
    'EXPORT_CAMERA' : "Set Sketch's camera view similar to Blender's",
    'EXPORT_GROUPS' : "Export groups",
    'OUTPUT_LANGUAGE' :
        'Set to tikz or pstricks. Output language generated by Sketch',
    'STANDALONE' : 'Output a standalone document',
    'USE_TWOSIDED_FLAG' :
        'Disable culling for objects with double sided option set',
    'SPE_LINE_COLOR' :
        'Use the specular color of the material as polygon line color',
    'ONLY_PROPERTIES' :
        'Use only properties for materials with the polyoptions and/or '\
        'lineoptions set',
}

def update_registry():
    d = {
        'EXPORT_MATERIALS' : EXPORT_MATERIALS,
        'OUTPUT_LANGUAGE' : OUTPUT_LANGUAGE,
        'USE_TWOSIDED_FLAG' : USE_TWOSIDED_FLAG,
        'EXPORT_CAMERA' : EXPORT_CAMERA,
        'STANDALONE' : STANDALONE,
        'SPE_LINE_COLOR' : SPE_LINE_COLOR,
        'ONLY_PROPERTIES' : ONLY_PROPERTIES,
        'EXPORT_GROUPS' : EXPORT_GROUPS,
    }
    Registry.SetKey(REG_KEY, d, True)

# Looking for a saved key in Blender.Registry dict:
rd = Registry.GetKey(REG_KEY, True)

if rd:
    try:
        OUTPUT_LANGUAGE = rd['OUTPUT_LANGUAGE']
        EXPORT_MATERIALS = rd['EXPORT_MATERIALS']
        USE_TWOSIDED_FLAG = rd['USE_TWOSIDED_FLAG']
        EXPORT_CAMERA = rd['EXPORT_CAMERA']
        STANDALONE = rd['STANDALONE']
        SPE_LINE_COLOR = rd['SPE_LINE_COLOR']
        ONLY_PROPERTIES = rd['ONLY_PROPERTIES']
        EXPORT_GROUPS = rd['EXPORT_GROUPS']
    except KeyError:
        print "Keyerror"
        update_registry()
else:
    update_registry()

# Start of GUI section ------------------------------------------------

from Blender import Draw

def draw_GUI():
    global EXPORT_MATERIALS, EXPORT_CAMERA, STANDALONE,USE_TWOSIDED_FLAG
    global OUTPUT_LANGUAGE, SPE_LINE_COLOR,ONLY_PROPERTIES, EXPORT_GROUPS
    format = Draw.Create(OUTPUT_LANGUAGE)
    mattog = Draw.Create(EXPORT_MATERIALS)
    cameratog = Draw.Create(EXPORT_CAMERA)
    groupstog = Draw.Create(EXPORT_GROUPS)
    standalonetog = Draw.Create(STANDALONE)
    doublesidedtog = Draw.Create(USE_TWOSIDED_FLAG)
    specoltog = Draw.Create(SPE_LINE_COLOR)
    onlyproptog = Draw.Create(ONLY_PROPERTIES)
    block = []

    block.append(("Output format: ", format, 0, 30,
        tooltips["OUTPUT_LANGUAGE"]))
    block.append("Export:")
    block.append(("Materials", mattog, tooltips['EXPORT_MATERIALS']))
    block.append(("Camera", cameratog, tooltips['EXPORT_CAMERA']))
    block.append(("Groups", groupstog, tooltips['EXPORT_GROUPS']))
    block.append("Material options:")
    block.append(("Spe line color",specoltog,tooltips['SPE_LINE_COLOR']))
    block.append(("Only properties",onlyproptog,tooltips['ONLY_PROPERTIES']))
    block.append("Other options:")
    block.append(("Standalone", standalonetog, tooltips['STANDALONE']))
    block.append(("Double sided", doublesidedtog,
        tooltips['USE_TWOSIDED_FLAG']))
    
    retval = Blender.Draw.PupBlock("Blend2Sketch options", block)
    if retval:
        # set options
        if format in ['tikz','pstricks']:
            OUTPUT_LANGUAGE = format.val
        else:
            print "Invalid output format %s" % format
        EXPORT_MATERIALS = mattog.val
        USE_TWOSIDED_FLAG = doublesidedtog.val
        STANDALONE = standalonetog.val
        EXPORT_CAMERA = cameratog.val
        SPE_LINE_COLOR = specoltog.val
        ONLY_PROPERTIES = onlyproptog.val
        EXPORT_GROUPS = groupstog.val
        update_registry()
    return retval


# End of GUI section ----------------------

# End of configuration section ---------

used_materials = {}
drawables = []

def sketchify(name):
    """Return a valid Sketch identifier"""
    return name.replace('.','_')

def get_material(material, is_face):
    """Convert material to Sketch options"""
    if not material:
        return ""
    opts = ""
    mat_name = sketchify(material.name)
    if is_face:
        try:
            if material.properties['polyoptions']:
                opts = "%s_poly" % (mat_name)
        except:
             opts = "%s_poly" % mat_name
    else:
        try:
            if material.properties['lineoptions']:
                opts = "%s_line" % (mat_name)
        except:
             opts = "%s_line" % mat_name
    used_materials[mat_name] = material
    return opts


def write_object(obj):
    """Write drawables and empties"""
    s = ""
    name = obj.name
    defname = sketchify(obj.name)
    prop = obj.properties
    
    mtrx = obj.matrix.rotationPart()
    xvec,yvec,zvec = mtrx[0],mtrx[1],mtrx[2]
    x,y,z = obj.getLocation('worldspace')
    if obj.type == 'Empty':
        if obj.parent:
            # need to transform Empty to world coordinates
            #vec = Mathutils.Vector(obj.getLocation())
            #nvec = vec*obj.matrixWorld
            #x,y,z = nvec.x, nvec.y, nvec.z
            x,y,z = obj.getLocation('worldspace')
        else:
            x,y,z = obj.loc
        s += "def %s (%s,%s,%s)\n" % (sketchify(name),x,y,z)
    elif obj.type in ['Mesh','Curve','Surf','MBall']:
        is_curve = (obj.type=='Curve')
        is_mesh = (obj.type == 'Mesh')
        drawables.append(sketchify(name))

        # write mesh to file
        
        if TRANSFORM_VERTICES:
            # The transformation code is from the Blender Python API
            # documentation.
            mesh = Mesh.New()
            mesh.getFromObject(obj.name)
            verts = mesh.verts[:]
            mesh.transform(obj.matrix)
        else:
            if not is_mesh:
                mesh= Mesh.New()
                mesh.getFromObject(obj.name)
            else:
                # Ensure that we get a mesh of the Mesh type
                mesh = obj.getData(mesh=True)
        twosided = (mesh.mode & Mesh.Modes['TWOSIDED']) and USE_TWOSIDED_FLAG
        s += 'def %s_o (%f,%f,%f)\n' % (defname,x,y,z)
        s += 'def %s_x [%f,%f,%f]\n' % tuple([defname]+list(xvec))
        s += 'def %s_y [%f,%f,%f]\n' % tuple([defname]+list(yvec))
        s += 'def %s_z [%f,%f,%f]\n' % tuple([defname]+list(zvec))
        polyoptsname = "%s_polyopts" % defname
        polyoptions = []
        try:
            polyopts = prop['polyoptions']
            polyoptions.append(polyopts)
        except:
            pass
        if twosided:
            polyoptions.append('cull=false')
        if polyoptions:
            s += 'def %s [%s]\n' % (polyoptsname,",".join(polyoptions))
        else:
            polyoptsname = ""
        lineoptsname = ""
        try:
            lineopts = prop['lineoptions']
            lineoptsname = "%s_lineopts" % defname
            s += 'def %s [%s]\n' % (lineoptsname,lineopts)
        except:
            lineoptsname = ""
        s += 'def %s {\n' % (defname)
        
        if EXPORT_MATERIALS:
            if is_mesh:
                materials = mesh.materials
            else:
                try:
                    materials = obj.data.getMaterials()
                    # does not work with MBall
                except:
                    materials = []
                    
        if len(mesh.faces) > 0:
            # iterate over all faces in the mesh
            

            for face in mesh.faces:
                polyoptions= []
                # each face is represented as a polygon
                if EXPORT_MATERIALS and len(materials):
                    polymatopts = get_material(materials[face.mat],is_face=True)
                    polyoptions.append(polymatopts)
                if polyoptsname:
                    polyoptions.append(polyoptsname)
                
                if polyoptions:
                    polyoptions = "[%s]" % ",".join(polyoptions)
                else:
                    polyoptions = ""
                s += '  polygon%s' % polyoptions
                # iterate over the vertices in the mesh
                for vert in face.v:
                    v = mesh.verts[vert.index]
                    s +=  '(%f,%f,%f)' % tuple(v.co)
                s += '\n'
        else:
            prev = None
            connectededges = False
            lineoptions = []
            if EXPORT_MATERIALS and materials:
                # pick first material
                for mat in materials:
                    if mat:
                        linematopts = get_material(mat, is_face=False)
                        lineoptions.append(linematopts)
                        break
            if lineoptsname:
                lineoptions.append(lineoptsname)
            if lineoptions:
                lineoptions = "[%s]" % ",".join(lineoptions)
            else:
                lineoptions = ""
            for edge in mesh.edges:
                if prev and (prev.v2.co == edge.v1.co):
                    connectededges = True
                    s += '(%f,%f,%f)' % tuple(edge.v2.co)
                else:
                    if connectededges:
                        s += '\n'
                        connectededges=False
                    s += '  line%s (%f,%f,%f)(%f,%f,%f)' % \
                       (lineoptions, edge.v1.co.x, edge.v1.co.y, edge.v1.co.z,
                        edge.v2.co.x, edge.v2.co.y, edge.v2.co.z)
                prev = edge
            s += '\n'


        if TRANSFORM_VERTICES:
            # Restore vertices.
            mesh.verts = verts
        s += '}\n\n'
        
    return s

def write_materials(used_materials):
    c = "% Materials section \n"
    for material in used_materials.values():
        mat_name = sketchify(material.name)
        polyopts = lineopts = opts = ''
        proponly = ONLY_PROPERTIES
        try:
            proponly = material.properties['onlyproperties']
            if proponly and type(proponly) == str:
                proponly =  proponly.lower() not in ('0','false')
        except:
            pass
        try:
            polyopts = material.properties['polyoptions']
        except:
            pass

        try:
            lineopts = material.properties['lineoptions']
        except:
            pass

        rgb = material.rgbCol
        spec = material.specCol
        alpha = material.alpha
        flags = material.getMode()
        if not (proponly and ( polyopts and lineopts )):
            c += "special |\definecolor{%s}{rgb}{%s,%s,%s}|[lay=under]\n" \
                % tuple([mat_name]+rgb)
            if SPE_LINE_COLOR:
                spemat_name = "%s_l" % mat_name
                c += "special |\definecolor{%s}{rgb}{%s,%s,%s}|[lay=under]\n" \
                % tuple([spemat_name]+spec)

            opts = "%s=%s" % (FILL_STR, mat_name)
            if SPE_LINE_COLOR:
                opts += ",%s=%s" % (LINE_STR, spemat_name)
            if alpha < 1.0:
                opts += ",%s=%s"  % (OPACITY_STR,alpha)

        if polyopts and opts and not proponly:
            opts += ',' + polyopts
        else:
            opts = polyopts or opts

        c += "def %s_poly [%s]\n" % (mat_name,opts)
        opts = ''
        if not (lineopts and proponly):
            opts = "%s=%s" % (LINE_STR, mat_name)

        if lineopts and opts:
            opts += ','+lineopts
        else:
            opts = lineopts or opts

        c += "def %s_line [%s]\n" % (mat_name,opts)
    return c
    

def write_objects(filepath):
    """Write all selected objects to filepath"""
    # get all selected objects
    objects = Blender.Object.GetSelected()
    f = file(filepath, 'w')
    # write header to file
    f.write('# Generated by sketch_export.py v %s \n' % (__version__))
    
    # iterate over each object
    # Todo: Should I group meshes and objects?
    s = ""
    s += "% Mesh section\n\n"
    for obj in objects:
        s += write_object(obj)
    if used_materials:
        c = write_materials(used_materials)
        s = c+"\n"+s
        
    groups = Group.Get()
    c = ""
    if EXPORT_GROUPS and groups:
        s += "% Group section"
        for group in groups:
            objects = group.objects
            if objects:
                for obj in objects:
                    obj_name = sketchify(obj.name)
                    if obj_name in drawables:
                        c += "  {%s}\n" % obj_name
                if c:
                    group_name = sketchify(group.name)
                    s += "\ndef %s {\n%s}\n" % (group_name, c)
                    c = ""
                    
                    
                    
    # Write scene to file
    if drawables:
        s += "\ndef scene {\n"
        if EXPORT_CAMERA:
            scn = Blender.Scene.GetCurrent()
            cam = scn.objects.camera
            vec = Mathutils.Vector([0,0,-1])
            direction = cam.matrix.rotationPart()*vec
            loc = cam.getLocation('worldspace')
            s += " put {view((%f,%f,%f),(%f,%f,%f),[0,0,1])}{\n" % \
                 tuple(list(loc)+list(direction))
        for name in drawables:
            s += "  {%s}\n" % name
        if EXPORT_CAMERA:
            s += " }\n"
        s += "}\n"
        if STANDALONE:
            s += "\n"
            s += "{scene}\n"
            if OUTPUT_LANGUAGE == 'tikz':
                s += "global {language tikz}\n"
            
    f.write(s)
    f.close()

# Start of script -----------------------------------------------------

# Ensure that at leas one object is selected
if len(Blender.Object.GetSelected()) == 0:
    # no objects selected. Print error message and quit
    Blender.Draw.PupMenu('ERROR: Please select at least one mesh')
else:
    fname = bsys.makename(ext=".sk")
    retval = draw_GUI()
    
    # Set TikZ and PSTricks specific parameters
    if OUTPUT_LANGUAGE == 'tikz':
        FILL_STR = "fill"
        LINE_STR = "draw"
        OPACITY_STR = "fill opacity"
    else:
        FILL_STR = "fillcolor"
        LINE_STR = "linecolor"
        OPACITY_STR = "fill opacity"
    if retval:
        Blender.Window.FileSelector(write_objects, "Export SK", fname)
        pass
        #write_objects('D:/pycode/blendtools/sketchexporter/tests/basic.sk')
